//
// Created by hsj on 2024-1-10.
// 编译：gcc select_serve_win.c -LH:\mingw\mingw64\x86_64-w64-mingw32\lib -lws2_32 -o select_serve_win
// ！！！select 默认以条件触发方式工作！！！
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>

#ifdef WIN32
//windows minGW64
//inet_pton函数：_WIN32_WINNT
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600
#include <winsock2.h>
#include <ws2tcpip.h>
//#pragma comment(lib,"ws2_32.lib")
#else
//linux  gcc
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#endif
#define BUF_SIZE 100
void ErrorHandling(char *message);

int main(int argc,char *argv[]){
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAdr, clntAdr;
    TIMEVAL timeout ;
    /**
     *  windows下fd文件描述符非递归自增，所以不同于linux,但是FD_ZERO、FD_SET兼容linux
        typedef struct fd_ set{
            u_int fd_count;
            SOCKET fd_array[FD_SETSIZE];
        } fd_set;
    */
    fd_set reads, cpyReads;
    int adrSz;
    int strLen, fdNum, i;
    char buf[BUF_SIZE];
    char port[]="9998";

    if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
        ErrorHandling( "WSAStartup() error!");

    hServSock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&servAdr, 0,sizeof(servAdr));
    servAdr.sin_family=AF_INET;
    servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
    servAdr.sin_port=htons(atoi(/*argv[1]*/port));

    if(bind(hServSock, (SOCKADDR*) &servAdr, sizeof(servAdr))==SOCKET_ERROR)
        ErrorHandling("bind() error");

    if(listen(hServSock, 128)==SOCKET_ERROR)
        ErrorHandling( "listen() error");
    printf("select serve win listen on port:%s\n",port);

    FD_ZERO(&reads);
    FD_SET(hServSock, &reads);

    /**
        typedef struct timeval{
            long tv_sec;//seconds
            long tv_usec;//microseconds
        } TIMEVAL;
    */
    while (1){
        cpyReads=reads;
        timeout.tv_sec=5;
        timeout.tv_usec=0;

        if((fdNum=select(0, &cpyReads, 0, 0, &timeout))==SOCKET_ERROR)//平台select函数的第一个参数是为了保持与(包括Linux的) UNIX系列操作系统的兼容性而添加的，并没有特殊意义。
        {
            printf("error：%d, end\n",fdNum);
            break;//异常返回-1，结束循环
        }

        if(fdNum==0)//超时返回0，一个也没有
        {
            printf("timeout,not any client...\n");//超时返回，一个也没有
            continue;
        }
//        printf("reads.fd_count: %d, end\n",reads.fd_count);
//        printf("cpyReads.fd_count: %d, end\n",cpyReads.fd_count);

        for(i=0; i<reads.fd_count; i++){//***********原始的为准*************
            SOCKET socketTmp = reads.fd_array[i];
            if(FD_ISSET(socketTmp, &cpyReads)){//FD_ISSET(int fd, fd_set * fdset) :若参数fdset指向的变量中包含文件描述符fd的信息，则返回“真”。
                if(socketTmp==hServSock){ // connection request!
                    adrSz=sizeof(clntAdr);
                    hClntSock=accept(hServSock,(SOCKADDR*)&clntAdr, &adrSz);
                    FD_SET(hClntSock, &reads);
                    printf( "connected a client: %d \n", hClntSock);
                }
                else{ // read message[client]
                    strLen=recv(socketTmp, buf, BUF_SIZE, 0);
                    if(strLen<=0){ // close request!
                        FD_CLR(socketTmp, &reads);
                        closesocket(socketTmp);
                        printf("closed client: %d \n", socketTmp);
                    }
                    else
                    {
                        send(socketTmp, buf, strLen, 0); //echo back
                    }
                }
            }
        }
    }
    closesocket (hServSock) ;
    WSACleanup();

    return EXIT_SUCCESS;
}

void ErrorHandling( char *message ){
    fputs (message, stderr);
    fputc('\n', stderr);
    exit(1);
}
